package com.zenika.zenbinding.client.model;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.google.gwt.user.client.ui.Widget;
import com.zenika.zenbinding.client.converter.Converter;
import com.zenika.zenbinding.client.exception.ConverterException;
import com.zenika.zenbinding.client.exception.DataException;
import com.zenika.zenbinding.client.exception.DataMessage;
import com.zenika.zenbinding.client.exception.ValidatorException;
import com.zenika.zenbinding.client.feedback.FeedbackManager;
import com.zenika.zenbinding.client.handler.Handler;
import com.zenika.zenbinding.client.handler.ListBindingListener;
import com.zenika.zenbinding.client.util.BeanUtil;
import com.zenika.zenbinding.client.validator.ValidationContext;
import com.zenika.zenbinding.client.validator.Validator;

/**
 * A ListModel manages the access to a property of a BusinessObject, the
 * validation and redirect errors of Validation or Conversion to a
 * FeedbackManager. It is used in the case of the property has to be
 * synchronized with a selection of objects in a list. The property could be an
 * Array, a List or an Object.
 * 
 * @author Zenika
 */
public class ListModel implements Model, ListBindingListener {

	private BusinessObject source;
	private String propertyPath;
	private List validators;
	private FeedbackManager feedback;
	private boolean errorState;
	private DataMessage currentError;
	private boolean single;
	private String feedbackId;
	private Converter converter;
	private Handler handler;

	/**
	 * Build a ListModel.
	 * 
	 * @param source
	 *            Object containing the property to bind
	 * @param propertyPath
	 *            Path to access to the property
	 */
	public ListModel(BusinessObject source, String propertyPath) {
		this.source = source;
		this.propertyPath = propertyPath;
		this.validators = new ArrayList();
	}

	/**
	 * Build a ListModel.
	 * 
	 * @param source
	 *            Object containing the property to bind
	 * @param propertyPath
	 *            Path to access to the property
	 */
	public ListModel(BusinessObject source, String propertyPath,
			Converter converter) {
		this.source = source;
		this.propertyPath = propertyPath;
		this.converter = converter;
		this.validators = new ArrayList();
	}

	/**
	 * Return the list of objects contained in the property.
	 * 
	 * @return List
	 */
	public List getList(Handler handler) {
		Object object = null;

		try {
			object = BeanUtil.getProperty(this.source, this.propertyPath);
		} catch (DataException e1) {
			e1.printStackTrace();
		}

		if (object instanceof List) {
			this.single = false;
			List list = (List) object;
			return list;
		} else {
			this.single = true;
			List list = new ArrayList();

			try {
				if (this.converter != null)
					list.add(this.converter.fromObjectToString(object));
				else
					list.add(object);
				return list;

			} catch (ConverterException e) {
				e.printStackTrace();
				// FIXME
				// return list;
				return null;
			}
		}
	}

	/**
	 * Update the property from the list selected by the user.
	 * 
	 * @param selectedItems
	 *            SList of selected items
	 * @param handler
	 *            The handler which had sent the selection
	 */
	public void updateList(List selectedItems, Handler handler) {
		try {
			// try to validate the new value
			this.validate(selectedItems);

			if (this.errorState) {
				this.errorState = false;
				this.currentError = null;
				this.fireErrorStop(handler);
			}

			// update the model, everything went fine
			this.updateValue(selectedItems);

		} catch (ValidatorException e) {
			// still update the model
			this.updateValue(selectedItems);

			if (!this.errorState) {
				this.errorState = true;
				this.currentError = e.getDataMessage();
				this.fireError(this.currentError, handler);
			}
		}
	}

	private void updateValue(List selectedItems) {
		try {
			if (this.single) {
				if (this.converter != null) {
					BeanUtil.setProperty(this.source, this.propertyPath,
							this.converter
									.fromStringToObject((String) selectedItems
											.get(0)));
				} else {
					BeanUtil.setProperty(this.source, this.propertyPath,
							selectedItems.get(0));
				}

			} else {
				BeanUtil.setProperty(this.source, this.propertyPath,
						selectedItems);
			}

		} catch (ConverterException e) {
			if (!this.errorState) {
				this.errorState = true;
				this.currentError = e.getDataMessage();
				this.fireError(this.currentError, handler);
			}
		}
	}

	/**
	 * Validate the object. If the validation fails, a ValidatorException is
	 * thrown.
	 * 
	 * @param object
	 *            Object to validate
	 * @throws ValidatorException
	 */
	public void validate(List selectedItems) throws ValidatorException {
		Iterator iter = this.validators.iterator();
		while (iter.hasNext()) {
			((Validator) iter.next()).validate(selectedItems);
		}
	}

	public void onChange(List selectedItems, Handler handler) {
		this.updateList(selectedItems, handler);
	}

	public void setFeedbackManager(FeedbackManager manager) {
		this.feedback = manager;
	}

	public void addValidator(Validator validator) {
		this.validators.add(validator);
	}

	public void addValidator(ValidationContext context, Validator validator) {
		if (validator != null)
			this.validators.add(validator);
		context.addModel(this);
	}

	public void fireError(DataMessage message, Handler handler) {
		if (this.feedbackId != null) {
			message.setMessage(message.getMessage() + " : " + this.feedbackId);
		}
		this.feedback.enterErrorState(message, handler);
	}

	public void fireErrorStop(Handler handler) {
		this.feedback.leaveErrorState(handler);
	}

	public void setFeedbackManager(FeedbackManager manager, String feedbackId) {
		this.setFeedbackManager(manager);
		this.feedbackId = feedbackId;
	}

	/**
	 * Specify the converter to use with this model. If you don't do it, a
	 * converter is automatically selected.
	 * 
	 * @param converter
	 *            Converter
	 */
	public void setConverter(Converter converter) {
		this.converter = converter;
	}

	public void setHandler(Handler handler) {
		this.handler = handler;
	}

	public Handler getHandler() {
		return this.handler;
	}

	public Widget getWidget() {
		return this.handler == null ? null : this.handler.getWidget();
	}

	public DataMessage getCurrentValidationState() {
		return this.currentError;
	}

	public DataMessage validate() {
		try {
			this.validate(this.getList(this.handler));
			return null;

		} catch (ValidatorException ve) {
			if (this.feedbackId != null && ve.getDataMessage() != null) {
				ve.getDataMessage().setMessage(
						ve.getDataMessage().getMessage() + " : "
								+ this.feedbackId);
			}
			return ve.getDataMessage();
		}
	}

}
